﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using Newtonsoft.Json;

namespace RtspTransport
{
    class Program
    {
        static TcpListener server;
        static Config config;
        static Dictionary<IntPtr, string> RtspAuthInfo { get;} = [];

        static readonly string[] separator = ["\r\n"];

        static void Main(string[] args)
        {
            //Authorization("DESCRIBE rtsp://192.168.133.50:10000/south_one_old_111 RTSP/1.0\r\nCSeq: 4\r\nAuthorization: Digest username=\"\", realm=\"48ea638a491f\", nonce=\"1635112212115311111122127441151334134735\", uri=\"rtsp://192.168.133.50:10000/south_one_old_111\", response=\"00244d09c5ce1089efca62b5bfb3ae67\"\r\nUser-Agent: libEasyRTSPClient v3.0.19.0415 (Core v2016.05.20)\r\nAccept: application/sdp\r\n\r\n");
            if (!File.Exists("config.json"))
            {
                Console.WriteLine("请检查配置文件，配置文件不存在！");
                Console.ReadLine();
                return;
            }
            try
            {
                config = JsonConvert.DeserializeObject<Config>(File.ReadAllText("config.json"));
            }
            catch
            {
                Console.WriteLine("请检查配置文件，配置文件格式不正确！");
                Console.ReadLine();
                return;
            }
            server = new TcpListener(IPAddress.Any, config.Port);
            try
            {
                server.Start();
                new Thread(new ThreadStart(StartTransport)).Start();
            }
            catch
            {
                Console.WriteLine("端口监听失败，请检查端口是否被占用！");
            }
            Console.ReadLine();
        }

        static void StartTransport()
        {
            while (true)
            {
                try
                {
                    var src = server.AcceptTcpClient();
                    var ns1 = src.GetStream();
                    var bt = new byte[10240];
                    var count = ns1.Read(bt, 0, bt.Length);
                    var str = Encoding.ASCII.GetString(bt, 0, count);
                    var srcTransportInfo = GetTransportInfo(str);
                    if (srcTransportInfo == null)
                    {
                        Console.WriteLine("未找到目标IP地址，关闭连接");
                        src.Close();
                        src.Dispose();
                        continue;
                    }
                    var dst = new TcpClient(srcTransportInfo.DstIP, srcTransportInfo.DstPort);
                    //设定超时，否则端口将一直被占用，即使失去连接
                    src.SendTimeout = 300000;
                    src.ReceiveTimeout = 300000;
                    dst.SendTimeout = 300000;
                    dst.ReceiveTimeout = 300000;
                    srcTransportInfo.Src = src;
                    srcTransportInfo.Dst = dst;
                    var dstTransportInfo = new TransportInfo() { Src = srcTransportInfo.Dst, Dst = srcTransportInfo.Src, NeedReplace = false };
                    var srcThread = new Thread(new ParameterizedThreadStart(Transfer));
                    srcThread.Start(srcTransportInfo);
                    var dstTHread = new Thread(new ParameterizedThreadStart(Transfer));
                    dstTHread.Start(dstTransportInfo);
                    var buf = Encoding.ASCII.GetBytes(str.Replace(srcTransportInfo.SrcAddress, srcTransportInfo.DstAddress));
                    dst.GetStream().Write(buf, 0, buf.Length);
                    Console.WriteLine($"TCP发起连接 源 {srcTransportInfo.SrcAddress} 转发至 {srcTransportInfo.DstAddress} 已建立连接");
                }
                catch { continue; }
            }
        }

        public static void Transfer(object obj)
        {
            var transportInfo = (TransportInfo)obj;
            var ns1 = transportInfo.Src.GetStream();
            var ns2 = transportInfo.Dst.GetStream();
            while (true)
            {
                try
                {
                    //这里必须try catch，否则连接一旦中断程序就崩溃了
                    //要是弹出错误提示让机主看见那就囧了
                    var bt = new byte[10240];
                    var count = ns1.Read(bt, 0, bt.Length);
                    if (count == 0) continue;
                    var str = Encoding.ASCII.GetString(bt, 0, count);
                    if (string.IsNullOrEmpty(str)) continue;
                    if (transportInfo.NeedReplace)
                    {
                        str = str.Replace(transportInfo.SrcAddress, transportInfo.DstAddress);
                        Console.WriteLine(str);
                        //如果需要授权需要重新计算response
                        if (str.Contains("Authorization: Digest"))
                        {
                            var response = GetValueByRegex(str, "response");
                            var uri = transportInfo.DstAddress;
                            var username = transportInfo.User;
                            var password = transportInfo.Password;
                            var realm = GetValueByRegex(str, "realm");
                            var nonce = GetValueByRegex(str, "nonce");
                            if ((string.IsNullOrEmpty(realm) || string.IsNullOrEmpty(nonce)) &&
                                RtspAuthInfo.TryGetValue(transportInfo.Src.Client.Handle, out string value))
                            {
                                var s = value.Split(';');
                                realm = s[0];
                                nonce = s[1];
                            }
                            var cmd = "OPTIONS";
                            if (str.Contains("DESCRIBE"))
                                cmd = "DESCRIBE";
                            else if (str.Contains("SETUP"))
                                cmd = "SETUP";
                            else if (str.Contains("TEARDOWN"))
                                cmd = "TEARDOWN";
                            else if (str.Contains("PLAY"))
                                cmd = "PLAY";
                            else if (str.Contains("PAUSE"))
                                cmd = "PAUSE";
                            else if (str.Contains("GET_PARAMETER"))
                                cmd = "GET_PARAMETER";
                            else if (str.Contains("SET_PARAMETER"))
                                cmd = "SET_PARAMETER";
                            var str1 = $"{username}:{realm}:{password}";
                            var str2 = $"{cmd}:{uri}";
                            var v1 = BitConverter.ToString(MD5.HashData(Encoding.ASCII.GetBytes(str1))).Replace("-", string.Empty).ToLower();
                            var v2 = BitConverter.ToString(MD5.HashData(Encoding.ASCII.GetBytes(str2))).Replace("-", string.Empty).ToLower();
                            var str3 = $"{v1}:{nonce}:{v2}";
                            var v3 = BitConverter.ToString(MD5.HashData(Encoding.ASCII.GetBytes(str3))).Replace("-", string.Empty).ToLower();

                            
                            //Console.Write($"u = {username}, realm = {realm}, nonce = {nonce}, uri = {uri}, response = {v3}");
                            str = Authorization(str, username, realm, nonce, uri, v3);
                            Console.WriteLine(str);
                            bt = Encoding.ASCII.GetBytes(str);
                            count = bt.Length;
                        }
                        else if(str.Contains("Authorization: Basic"))
                        {
                            var basicStr = $"{transportInfo.User}:{transportInfo.Password}";
                            var basicBuf = Encoding.UTF8.GetBytes(basicStr);
                            var basic = Convert.ToBase64String(basicBuf);
                            str = Authorization(str, basic);
                            Console.WriteLine(str);
                            bt = Encoding.ASCII.GetBytes(str);
                            count = bt.Length;
                        }
                    }
                    else if (str.Contains("RTSP/1.0 401"))
                    {
                        var Realm = GetValueByRegex(str, "realm");
                        var Nonce = GetValueByRegex(str, "nonce");
                        if (RtspAuthInfo.ContainsKey(transportInfo.Dst.Client.Handle))
                            RtspAuthInfo[transportInfo.Dst.Client.Handle] = $"{Realm};{Nonce}";
                        else
                            RtspAuthInfo.Add(transportInfo.Dst.Client.Handle, $"{Realm};{Nonce}");
                    }
                    ns2.Write(bt, 0, count);
                }
                catch
                {
                    Console.WriteLine($"关闭连接 {transportInfo.SrcAddress}");
                    if (RtspAuthInfo.ContainsKey(transportInfo.Dst.Client.Handle))
                        RtspAuthInfo.Remove(transportInfo.Dst.Client.Handle);
                    if (RtspAuthInfo.ContainsKey(transportInfo.Src.Client.Handle))
                        RtspAuthInfo.Remove(transportInfo.Src.Client.Handle);
                    ns1.Close();
                    ns2.Close();
                    ns1.Dispose();
                    ns2.Dispose();
                    break;
                }
            }
        }

        static string Authorization(string str, string u, string r, string n, string url, string res)
        {
            var strs = str.Split(separator, StringSplitOptions.None);
            foreach (var s in strs)
            {
                if (s.Contains("Authorization"))
                {
                    return str.Replace(s, $"Authorization: Digest username=\"{u}\", realm=\"{r}\", nonce=\"{n}\", uri=\"{url}\", response=\"{res}\"");
                }
            }
            return string.Empty;
        }

        static string Authorization(string str, string basic)
        {
            var strs = str.Split(separator, StringSplitOptions.None);
            foreach (var s in strs)
            {
                if (s.Contains("Authorization"))
                {
                    return str.Replace(s, $"Authorization: Basic {basic}");
                }
            }
            return string.Empty;
        }
        static TransportInfo GetTransportInfo(string str)
        {
            var info = new TransportInfo();
            var rtsp = ParseRTSPInfo(str);
            if (rtsp == null) return null;
            info.SrcIP = rtsp.IP;
            info.SrcPort = int.Parse(rtsp.Port);
            info.SrcAddress = rtsp.URL;
            var s = rtsp.URL.LastIndexOf('/') + 1;
            var l = rtsp.URL.Length - s;
            var key = rtsp.URL.Substring(s, l);
            if (!config.Streams.TryGetValue(key, out string value)) return null;
            var DstRtsp = ParseRTSPInfo(value);
            info.DstIP = DstRtsp.IP;
            info.DstPort = int.Parse(DstRtsp.Port);
            info.DstAddress = DstRtsp.URL.Replace($"{DstRtsp.User}:{DstRtsp.Password}@", string.Empty);
            info.User = DstRtsp.User;
            info.Password = DstRtsp.Password;
            info.NeedReplace = true;
            return info;
        }

        static RTSPInfo ParseRTSPInfo(string str)
        {
            var rtspInfo = str.Split(separator, StringSplitOptions.None);
            if (rtspInfo.Length < 1) return null;
            var rtspRequest = rtspInfo[0];
            if (!rtspRequest.Contains("rtsp://")) return null;
            var request = rtspRequest.Split(' ');
            var rtsp = new RTSPInfo();
            if (request.Length > 1)
                rtsp.URL = request[1];
            else
                rtsp.URL = request[0];
            var s = rtsp.URL.LastIndexOf('@') + 1;
            if (s == 0)
                s = rtsp.URL.IndexOf("//") + 2;
            var l = rtsp.URL.IndexOf('/', rtsp.URL.IndexOf("//") + 2) - s;
            if (l < 0) l = rtsp.URL.Length - s;
            var addr = rtsp.URL.Substring(s, l);
            rtsp.IP = addr.Split(':')[0];
            rtsp.Port = "554";
            if (addr.Split(':').Length > 1)
                rtsp.Port = addr.Split(':')[1];
            if (rtsp.URL.Contains('@'))
            {
                s = rtsp.URL.IndexOf("//") + 2;
                l = rtsp.URL.LastIndexOf('@') - s;
                var auth = rtsp.URL.Substring(s, l);
                rtsp.User = auth.Split(':')[0];
                rtsp.Password = auth.Split(':')[1];
            }
            return rtsp;
        }

        static string GetValueByRegex(string src, string key)
        {
            var m = Regex.Match(src, $"{key}=\"(.+?)\"");
            string v = m.Value;
            if (string.IsNullOrEmpty(v)) return string.Empty;
            return v.Replace($"{key}=", string.Empty).Replace("\"", string.Empty);
        }

        class TransportInfo
        {
            public string SrcAddress;
            public string SrcIP;
            public int SrcPort;
            public TcpClient Src;
            public string DstAddress;
            public string DstIP;
            public int DstPort;
            public TcpClient Dst;
            public string User;
            public string Password;
            public bool NeedReplace;
        }

        class RTSPInfo
        {
            public string URL;
            public string User;
            public string Password;
            public string IP;
            public string Port;
        }

        class Config
        {
            /// <summary>
            /// 监听端口
            /// </summary>
            public int Port;
            /// <summary>
            /// 取流配置
            /// </summary>
            public Dictionary<string, string> Streams;
        }
    }
}
